

#define LG2D_CORE

#include "lg2d_obj.h"

static void
updateTransfor(LG2DObj* obj, LGMatrix* m) {
	LGMatrix transform;
	lgIdentityMatrix(&transform);
	lgScaleMatrix(&transform, &obj->scale);
	lgRotateMatrix2D(&transform, obj->rotate);
	lgMoveMatrix(&transform, &obj->position);
	obj->transform = *m;
	lgMulMatrix(&obj->transform, &transform);
}

static void
removeChild(LG2DObj* parent, LG2DObj* child) {
	if (child->parent != parent) {
		return;
	}
	for (unsigned i = child->index; i < (parent->count - 1); i++) {
		parent->children[i] = parent->children[i + 1];
	}
}

static void
removeAllChild(LG2DObj* obj) {
	LG2DObj* child;
	for (unsigned i = 0; i < obj->count; i++) {
		child = obj->children[i];
		child->parent = NULL;
		lgReleaseRef(child);
	}
	obj->count = 0;
}

static void
expandChildren(LG2DObj* obj) {
	unsigned cap = obj->cap * 2;
	LG2DObj** children = malloc(sizeof(LG2DObj*) * cap);
	for (unsigned i = 0; i < obj->count; i++) {
		children[i] = obj->children[i];
	}
	if (obj->children != obj->childrenCache) {
		free(obj->children);
	}
	obj->cap = cap;
	obj->children = children;
}

static void
freeObj(void* ref) {
	LG2DObj* obj = (LG2DObj*)ref;
	if (obj->free) {
		obj->free(ref);
	}
	if (obj->parent) {
		removeChild(obj->parent, obj);
	}
	removeAllChild(obj);
	if (obj->children != obj->childrenCache) {
		free(obj->children);
	}
}


LG2D_API void*
lg2dNewObj() {
	return lg2dNewExtendObj(sizeof(LG2DObj), NULL);
}

LG2D_API void*
lg2dNewExtendObj(size_t size, LGRefFree free) {
	LG2DObj* obj = lgNewRef(size, freeObj);
	LGVector scale = { 1.f, 1.f, 1.f, 1.f };
	LGVector position = { 0.f, 0.f, 0.f, 1.f };
	obj->position = position;
	obj->scale = scale;
	obj->rotate = 0.f;
	obj->children = obj->childrenCache;
	obj->cap = 8;
	obj->dirty = 1;
	obj->free = free;
	obj->visible = 1;
	return obj;
}

LG2D_API void
lg2dAddChild(void* p, void* c) {
	LG2DObj* parent = (LG2DObj*)p;
	LG2DObj* child = (LG2DObj*)c;
	if (child->parent) {
		return;
	}
	lgRetainRef((LGRef*)child);
	child->parent = parent;
	if (parent->count >= parent->cap) {
		expandChildren(parent);
	}
	parent->children[parent->count++] = child;
}

LG2D_API void
lg2dRemoveChild(void* p, void* c) {
	removeChild((LG2DObj*)p, (LG2DObj*)c);
	lgReleaseRef(c);
}

LG2D_API void
lg2dSetPosition(void* p, LGFloat x, LGFloat y) {
	LG2DObj* obj = (LG2DObj*)p;
	obj->position.x = x;
	obj->position.y = y;
	obj->dirty = 1;
}

LG2D_API void
lg2dSetRotate(void* p, LGFloat rotate) {
	LG2DObj* obj = (LG2DObj*)p;
	obj->rotate = rotate;
	obj->dirty = 1;
}

LG2D_API void
lg2dSetScale(void* p, LGFloat x, LGFloat y) {
	LG2DObj* obj = (LG2DObj*)p;
	obj->scale.x = x;
	obj->scale.y = y;
	obj->dirty = 1;
}

void
lg2dVisitObj(void* p, LGMatrix* transform, int force) {
	LG2DObj* obj = (LG2DObj*)p;
	if (!obj->visible) {
		return;
	}
	if (obj->dirty || force) {
		updateTransfor(obj, transform);
		if (obj->visit) {
			obj->visit(obj, transform, force);
		}
	}
	LG2DObj* child;
	for (unsigned i = 0; i < obj->count; i++) {
		child = obj->children[i];
		lg2dVisitObj(child, &obj->transform, obj->dirty || force);

	}
	obj->dirty = 0;
}

extern void
lg2dDrawObj(LG2DScene* scene, LG2DObj* obj) {
	if (obj->draw) {
		obj->draw(scene, obj);
	}
	LG2DObj* child;
	for (unsigned i = 0; i < obj->count; i++) {
		child = obj->children[i];
		lg2dDrawObj(scene, child);
	}
}